/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the demonstration applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "QxMenuManager.hpp"
#include "QxColors.hpp"
#include "QxMenuContent.hpp"
#include "QxExampleContent.hpp"

#include "QxAllSampleFactory.hpp"

QxMenuManager *QxMenuManager::pInstance = 0;

class QxMenuManager::QxMenuManagerPrivate
{
public:

  QxMenuManagerPrivate( QxMenuManager* me ) : m_self( me ) {
    m_ticker             = 0;
    m_tickerInAnim       = 0;
    m_upButton           = 0;
    m_downButton         = 0;
    m_helpEngine         = 0;
    m_score              = new QxScore();
    m_currentMenu        = QLatin1String( "[no menu visible]" );
    m_currentCategory    = QLatin1String( "[no category visible]" );
    m_currentMenuButtons = QLatin1String( "[no menu buttons visible]" );
    m_currentInfo        = QLatin1String( "[no m_info visible]" );
    m_currentMenuCode    = -1;

    initHelpEngine();
  }

  ~QxMenuManagerPrivate() {
    delete m_score;
    delete m_helpEngine;
  }

  void init( QxMainWindow* window ) {
    m_window = window;

    // Create div:
    createTicker();
    createUpnDownButtons();
    createBackButton();

    createRootMenu( &theAllSampleFactory );
    Q_FOREACH( QxAbstractFactory* factory, theAllSampleFactory.factories() ) {
      if( factory == &theAllSampleFactory ) {
        continue;
      }
      createSubMenu( factory );

      Q_FOREACH( QxAbstractPlugin* plugin, factory->getPlugins<QxAbstractPlugin>() ) {
        readInfoAboutExample( plugin );
        createLeafMenu( plugin );
      }
    }
  }

  QByteArray getResource( const QString& name ) {
    QByteArray ba = this->m_helpEngine->fileData( name );
    if( QxColors::verbose && ba.isEmpty() ) {
      qDebug() << " - WARNING: Could not get " << name;
    }
    return ba;
  }

  void initHelpEngine() {
    m_helpRootUrl = QString( "qthelp://com.trolltech.qt.%1%2%3/qdoc/" ).arg( QT_VERSION >> 16 ).arg( (QT_VERSION >> 8) & 0xFF ).arg( QT_VERSION & 0xFF );

    // Store help collection file in cache dir of assistant
    QString cacheDir = QDesktopServices::storageLocation( QDesktopServices::DataLocation )+QLatin1String( "/Trolltech/Assistant/" );
    QString helpDataFile = QString( QLatin1String( "qtdemo_%1.qhc" ) ).arg( QLatin1String( QT_VERSION_STR ) );

    QDir dir;
    if( !dir.exists( cacheDir ) ) {
      dir.mkpath( cacheDir );
    }

    // Create help engine (and new
    // helpDataFile if it does not exist):
    m_helpEngine = new QHelpEngineCore( cacheDir + helpDataFile );
    m_helpEngine->setupData();

    QString qtDocRoot = QLibraryInfo::location( QLibraryInfo::DocumentationPath ) + QLatin1String( "/qch" );
    qtDocRoot = QDir( qtDocRoot ).absolutePath();

    QStringList qchFiles;
    qchFiles << QLatin1String( "/qt.qch" ) << QLatin1String( "/designer.qch" ) << QLatin1String( "/linguist.qch" );

    QString oldDir = m_helpEngine->customValue( QLatin1String( "m_docDir" ), QString()).toString();
    if( oldDir != qtDocRoot ) {
      foreach( const QString& qchFile, qchFiles ) {
        m_helpEngine->unregisterDocumentation( QHelpEngineCore::namespaceName( qtDocRoot + qchFile ) );
      }
    }

    // If the data that the engine will work
    // on is not yet registered, do it now:
    foreach( const QString& qchFile, qchFiles ) {
      m_helpEngine->registerDocumentation( qtDocRoot + qchFile );
    }
    m_helpEngine->setCustomValue( QLatin1String( "m_docDir" ), qtDocRoot );
  }

  void getDocumentationDir() {
  }

  void readInfoAboutExample( const QDomElement& example ) {
    QString name = example.attribute( "name" );
    if( m_info.contains( name ) )
      qWarning() << "__WARNING: QxMenuManager::readInfoAboutExample: Demo/example with name"
                 << name
                 << "appears twice in the xml-file!__";

    m_info[name]["filename"]        = example.attribute( "filename");
    m_info[name]["category"]        = example.parentNode().toElement().tagName();
    m_info[name]["dirname"]         = example.parentNode().toElement().attribute( "dirname" );
    m_info[name]["changedirectory"] = example.attribute( "changedirectory" );
    m_info[name]["image"]           = example.attribute( "image" );
    m_info[name]["qml"]             = example.attribute( "qml" );
  }

  void readInfoAboutExample( const QxAbstractPlugin* example ) {
    QString name = example->name();
    if( m_info.contains( name ) ) {
      qWarning() << "__WARNING: QxMenuManager::readInfoAboutExample: Demo/example with name"
                 << name
                 << "appears twice in the xml-file!__";
    }

    QxAbstractFactory* factory = qobject_cast<QxAbstractFactory*>( example->parent() );

    m_info[name]["filename"]        = QFileInfo( example->absoluteFilename() ).baseName();
    m_info[name]["category"]        = factory->name();
    m_info[name]["dirname"]         = QFileInfo( example->absoluteFilename() ).absolutePath();
    m_info[name]["changedirectory"] = "";
    m_info[name]["image"]           = "";
    m_info[name]["qml"]             = "";
  }

  void showDocInAssistant( const QString& name ) {
    QString url = resolveDocUrl( name );
    if( QxColors::verbose ) {
      qDebug() << "Sending URL to Assistant:" << url;
    }

    // Start assistant if it's not already running:
    if( m_assistantProcess.state() != QProcess::Running ) {
      QString app = QLibraryInfo::location( QLibraryInfo::BinariesPath ) + QDir::separator();
  #if !defined( Q_OS_MAC )
      app += QLatin1String( "assistant" );
  #else
      app += QLatin1String( "Assistant.app/Contents/MacOS/Assistant" );
  #endif
      QStringList args;
      args << QLatin1String( "-enableRemoteControl" );
      m_assistantProcess.start( app, args );
      if( !m_assistantProcess.waitForStarted() ) {
        QMessageBox::critical( 0, tr( "Qt Demo" ), tr( "Could not start Qt Assistant." ).arg( app ) );
        return;
      }
    }

    // Send command through remote control even if the process
    // was started to activate assistant and bring it to front:
    QTextStream str( &this->m_assistantProcess );
    str << "SetSource " << url << QLatin1Char('\n') << endl;
  }

  void launchExample( const QString& name ) {
    QxAbstractPlugin* plugin = theAllSampleFactory.findPluginByName( name );
    if( plugin ) {
      QWidget* theWidget = plugin->createMainWidget();
      if( theWidget ) {
        theWidget->setAttribute( Qt::WA_DeleteOnClose );
        theWidget->show();
      }
    }
  }

  void createMenu( const QxAbstractPlugin* category, QxMenuManager::BUTTON_TYPE type ) {
    qreal sw = m_window->scene->sceneRect().width();
    int xOffset = 15;
    int yOffset = 10;
    int maxExamples = QxColors::menuCount;
    int menuIndex = 1;

    QString name = category->name();
    QString m_currentMenu = name + QLatin1String( " -menu" ) + QString::number( menuIndex );

    int idx = 0;
    Movie* movieIn = 0;
    Movie* movieOut = 0;
    Movie* movieNextTopOut = 0;
    Movie* movieNextBottomOut = 0;
    Movie* movieNextTopIn = 0;
    Movie* movieNextBottomIn = 0;
    Movie* movieShake = 0;

    Q_FOREACH( QObject* childNode, category->children() ) {
      QxAbstractPlugin* currentNode = qobject_cast<QxAbstractPlugin*>( childNode );
      if( !currentNode ) {
        continue;
      }

      if( !(idx % maxExamples) ) {
        movieIn            = m_score->insertMovie( m_currentMenu );
        movieOut           = m_score->insertMovie( m_currentMenu + " -out" );
        movieNextTopOut    = m_score->insertMovie( m_currentMenu + " -top_out" );
        movieNextBottomOut = m_score->insertMovie( m_currentMenu + " -bottom_out" );
        movieNextTopIn     = m_score->insertMovie( m_currentMenu + " -top_in" );
        movieNextBottomIn  = m_score->insertMovie( m_currentMenu + " -bottom_in" );
        movieShake         = m_score->insertMovie( m_currentMenu + " -shake" );
        idx = 0;
      }

      {
        // create normal menu button
        QString label = currentNode->name();
        QxTextButton* item = new QxTextButton( label, QxTextButton::LEFT, type, m_window->scene, m_window->mainSceneRoot );

        item->setRecursiveVisible( false );
        item->setZValue( 10 );
        qreal ih = item->sceneBoundingRect().height();
        qreal iw = item->sceneBoundingRect().width();
        qreal ihp = ih + 3;

        // create in-animation:
        QxDemoItemAnimation* anim = new QxDemoItemAnimation( item, QxDemoItemAnimation::ANIM_IN );
        anim->setDuration( float(1000 + (idx * 20)) * QxColors::animSpeedButtons );
        anim->setStartPos( QPointF(xOffset, -ih) );
        anim->setPosAt( 0.20, QPointF(xOffset, -ih) );
        anim->setPosAt( 0.50, QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY + (10 * float(idx / 4.0f))) );
        anim->setPosAt( 0.60, QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY) );
        anim->setPosAt( 0.70, QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY + (5 * float(idx / 4.0f))) );
        anim->setPosAt( 0.80, QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY) );
        anim->setPosAt( 0.90, QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY + (2 * float(idx / 4.0f))) );
        anim->setPosAt( 1.00, QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY) );
        movieIn->append( anim );

        // create out-animation:
        anim = new QxDemoItemAnimation( item, QxDemoItemAnimation::ANIM_OUT );
        anim->hideOnFinished = true;
        anim->setDuration( (700 + (30 * idx)) * QxColors::animSpeedButtons );
        anim->setStartPos( QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY) );
        anim->setPosAt( 0.60, QPointF(xOffset, 600 - ih - ih) );
        anim->setPosAt( 0.65, QPointF(xOffset + 20, 600 - ih) );
        anim->setPosAt( 1.00, QPointF(sw + iw, 600 - ih) );
        movieOut->append( anim );

        // create shake-animation:
        anim = new QxDemoItemAnimation( item );
        anim->setDuration( 700 * QxColors::animSpeedButtons );
        anim->setStartPos( QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY) );
        anim->setPosAt( 0.55, QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY - idx*2.0) );
        anim->setPosAt( 0.70, QPointF(xOffset - 10, (idx * ihp) + yOffset + QxColors::contentStartY - idx*1.5) );
        anim->setPosAt( 0.80, QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY - idx*1.0) );
        anim->setPosAt( 0.90, QPointF(xOffset - 2, (idx * ihp) + yOffset + QxColors::contentStartY - idx*0.5) );
        anim->setPosAt( 1.00, QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY) );
        movieShake->append( anim );

        // create next-menu top-out-animation:
        anim = new QxDemoItemAnimation( item, QxDemoItemAnimation::ANIM_OUT );
        anim->hideOnFinished = true;
        anim->setDuration( (200 + (30 * idx)) * QxColors::animSpeedButtons );
        anim->setStartPos( QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY) );
        anim->setPosAt( 0.70, QPointF(xOffset, yOffset + QxColors::contentStartY) );
        anim->setPosAt( 1.00, QPointF(-iw, yOffset + QxColors::contentStartY) );
        movieNextTopOut->append( anim );

        // create next-menu bottom-out-animation:
        anim = new QxDemoItemAnimation( item, QxDemoItemAnimation::ANIM_OUT );
        anim->hideOnFinished = true;
        anim->setDuration( (200 + (30 * idx)) * QxColors::animSpeedButtons );
        anim->setStartPos( QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY) );
        anim->setPosAt( 0.70, QPointF(xOffset, (maxExamples * ihp) + yOffset + QxColors::contentStartY) );
        anim->setPosAt( 1.00, QPointF(-iw, (maxExamples * ihp) + yOffset + QxColors::contentStartY) );
        movieNextBottomOut->append( anim );

        // create next-menu top-in-animation:
        anim = new QxDemoItemAnimation( item, QxDemoItemAnimation::ANIM_IN );
        anim->setDuration( (700 - (30 * idx)) * QxColors::animSpeedButtons );
        anim->setStartPos( QPointF(-iw, yOffset + QxColors::contentStartY) );
        anim->setPosAt( 0.30, QPointF(xOffset, yOffset + QxColors::contentStartY) );
        anim->setPosAt( 1.00, QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY) );
        movieNextTopIn->append( anim );

        // create next-menu bottom-in-animation:
        int reverse = maxExamples - idx;
        anim = new QxDemoItemAnimation( item, QxDemoItemAnimation::ANIM_IN );
        anim->setDuration( (1000 - (30 * reverse)) * QxColors::animSpeedButtons );
        anim->setStartPos( QPointF(-iw, (maxExamples * ihp) + yOffset + QxColors::contentStartY) );
        anim->setPosAt( 0.30, QPointF(xOffset, (maxExamples * ihp) + yOffset + QxColors::contentStartY) );
        anim->setPosAt( 1.00, QPointF(xOffset, (idx * ihp) + yOffset + QxColors::contentStartY) );
        movieNextBottomIn->append( anim );
      }

      if( !(++idx % maxExamples) ) {
        ++menuIndex;
        m_info[m_currentMenu]["more"] = name + QLatin1String( " -menu" ) + QString::number( menuIndex );
        m_currentMenu = name + QLatin1String( " -menu" ) + QString::number( menuIndex );
        m_info[m_currentMenu]["back"] = name + QLatin1String( " -menu" ) + QString::number( menuIndex - 1 );
      }
    }
  }

  void createLowLeftButton( const QString& label, QxMenuManager::BUTTON_TYPE type, Movie* movieIn, Movie* movieOut, Movie* movieShake, const QString& menuString=QString() ) {
    QxTextButton* button = new QxTextButton( label, QxTextButton::RIGHT, type, m_window->scene, m_window->mainSceneRoot, QxTextButton::PANEL );
    if( !menuString.isNull() ) {
      button->setMenuString( menuString );
    }
    button->setRecursiveVisible( false );
    button->setZValue( 10 );

    qreal iw = button->sceneBoundingRect().width();
    int xOffset = 15;

    // create in-animation:
    QxDemoItemAnimation* buttonIn = new QxDemoItemAnimation( button, QxDemoItemAnimation::ANIM_IN );
    buttonIn->setDuration( 1800 * QxColors::animSpeedButtons );
    buttonIn->setStartPos( QPointF(-iw, QxColors::contentStartY + QxColors::contentHeight - 35) );
    buttonIn->setPosAt( 0.5, QPointF(-iw, QxColors::contentStartY + QxColors::contentHeight - 35) );
    buttonIn->setPosAt( 0.7, QPointF(xOffset, QxColors::contentStartY + QxColors::contentHeight - 35) );
    buttonIn->setPosAt( 1.0, QPointF(xOffset, QxColors::contentStartY + QxColors::contentHeight - 26) );
    movieIn->append( buttonIn );

    // create out-animation:
    QxDemoItemAnimation* buttonOut = new QxDemoItemAnimation( button, QxDemoItemAnimation::ANIM_OUT );
    buttonOut->hideOnFinished = true;
    buttonOut->setDuration( 400 * QxColors::animSpeedButtons );
    buttonOut->setStartPos( QPointF(xOffset, QxColors::contentStartY + QxColors::contentHeight - 26) );
    buttonOut->setPosAt( 1.0, QPointF(-iw, QxColors::contentStartY + QxColors::contentHeight - 26) );
    movieOut->append( buttonOut );

    if( movieShake ) {
      QxDemoItemAnimation* shakeAnim = new QxDemoItemAnimation( button, QxDemoItemAnimation::ANIM_UNSPECIFIED );
      shakeAnim->timeline->setCurveShape( QTimeLine::LinearCurve );
      shakeAnim->setDuration( 650 );
      shakeAnim->setStartPos( buttonIn->posAt( 1.0f ) );
      shakeAnim->setPosAt( 0.60, buttonIn->posAt( 1.0f ) );
      shakeAnim->setPosAt( 0.70, buttonIn->posAt( 1.0f ) + QPointF(-3, 0) );
      shakeAnim->setPosAt( 0.80, buttonIn->posAt( 1.0f ) + QPointF(2, 0) );
      shakeAnim->setPosAt( 0.90, buttonIn->posAt( 1.0f ) + QPointF(-1, 0) );
      shakeAnim->setPosAt( 1.00, buttonIn->posAt( 1.0f ) );
      movieShake->append(shakeAnim);
    }
  }

  void createLowRightButton( const QString& label, QxMenuManager::BUTTON_TYPE type, Movie* movieIn, Movie* movieOut, Movie* /*movieShake*/) {
    QxTextButton* item = new QxTextButton( label, QxTextButton::RIGHT, type, m_window->scene, m_window->mainSceneRoot, QxTextButton::PANEL );
    item->setRecursiveVisible( false );
    item->setZValue( 10 );

    qreal sw = this->m_window->scene->sceneRect().width();
    int xOffset = 70;

    // create in-animation:
    QxDemoItemAnimation* anim = new QxDemoItemAnimation( item, QxDemoItemAnimation::ANIM_IN );
    anim->setDuration( 1800 * QxColors::animSpeedButtons );
    anim->setStartPos( QPointF(sw, QxColors::contentStartY + QxColors::contentHeight - 35) );
    anim->setPosAt( 0.5, QPointF(sw, QxColors::contentStartY + QxColors::contentHeight - 35) );
    anim->setPosAt( 0.7, QPointF(xOffset + 535, QxColors::contentStartY + QxColors::contentHeight - 35));
    anim->setPosAt( 1.0, QPointF(xOffset + 535, QxColors::contentStartY + QxColors::contentHeight - 26));
    movieIn->append( anim );

    // create out-animation:
    anim = new QxDemoItemAnimation( item, QxDemoItemAnimation::ANIM_OUT );
    anim->hideOnFinished = true;
    anim->setDuration( 400 * QxColors::animSpeedButtons );
    anim->setStartPos( QPointF(xOffset + 535, QxColors::contentStartY + QxColors::contentHeight - 26) );
    anim->setPosAt( 1.0, QPointF(sw, QxColors::contentStartY + QxColors::contentHeight - 26));
    movieOut->append( anim );
  }

  void createLowRightLeafButton( const QString& label, int xOffset, QxMenuManager::BUTTON_TYPE type, Movie* movieIn, Movie* movieOut, Movie* /*movieShake*/) {
    QxTextButton* item = new QxTextButton( label, QxTextButton::RIGHT, type, m_window->scene, m_window->mainSceneRoot, QxTextButton::PANEL );
    item->setRecursiveVisible( false );
    item->setZValue( 10 );

    qreal sw = m_window->scene->sceneRect().width();
    qreal sh = m_window->scene->sceneRect().height();

    // create in-animation:
    QxDemoItemAnimation* anim = new QxDemoItemAnimation( item, QxDemoItemAnimation::ANIM_IN );
    anim->setDuration( 1050 * QxColors::animSpeedButtons );
    anim->setStartPos( QPointF(sw, QxColors::contentStartY + QxColors::contentHeight - 35) );
    anim->setPosAt( 0.10, QPointF(sw, QxColors::contentStartY + QxColors::contentHeight - 35) );
    anim->setPosAt( 0.30, QPointF(xOffset, QxColors::contentStartY + QxColors::contentHeight - 35) );
    anim->setPosAt( 0.35, QPointF(xOffset + 30, QxColors::contentStartY + QxColors::contentHeight - 35) );
    anim->setPosAt( 0.40, QPointF(xOffset, QxColors::contentStartY + QxColors::contentHeight - 35) );
    anim->setPosAt( 0.45, QPointF(xOffset + 5, QxColors::contentStartY + QxColors::contentHeight - 35) );
    anim->setPosAt( 0.50, QPointF(xOffset, QxColors::contentStartY + QxColors::contentHeight - 35) );
    anim->setPosAt( 1.00, QPointF(xOffset, QxColors::contentStartY + QxColors::contentHeight - 26) );
    movieIn->append( anim );

    // create out-animation:
    anim = new QxDemoItemAnimation( item, QxDemoItemAnimation::ANIM_OUT );
    anim->hideOnFinished = true;
    anim->setDuration( 300 * QxColors::animSpeedButtons );
    anim->setStartPos( QPointF(xOffset, QxColors::contentStartY + QxColors::contentHeight - 26) );
    anim->setPosAt( 1.0, QPointF(xOffset, sh) );
    movieOut->append( anim );
  }

  void createRootMenu( const QxAbstractPlugin* el ) {
    QString name = el->name();
    createMenu( el, QxMenuManager::MENU1 );
    createInfo( new QxMenuContentItem( el, m_window->scene, m_window->mainSceneRoot ), name + " -m_info" );

    Movie* menuButtonsIn = m_score->insertMovie( name + " -buttons" );
    Movie* menuButtonsOut = m_score->insertMovie( name + " -buttons -out" );
    createLowLeftButton( QLatin1String( "Quit" ), QxMenuManager::QUIT, menuButtonsIn, menuButtonsOut, 0 );
    createLowRightButton( tr( "Toggle fullscreen" ), QxMenuManager::FULLSCREEN, menuButtonsIn, menuButtonsOut, 0 );
  }

  void createSubMenu( const QxAbstractPlugin* el ) {
    QString name = el->name();
    createMenu( el, QxMenuManager::MENU2 );
    createInfo( new QxMenuContentItem(el, m_window->scene, m_window->mainSceneRoot), name + " -m_info" );
  }

  void createLeafMenu( const QxAbstractPlugin* el ) {
    QString name = el->name();
    createInfo( new QxExampleContent(name, m_window->scene, m_window->mainSceneRoot), name );

    Movie* infoButtonsIn = m_score->insertMovie( name + " -buttons" );
    Movie* infoButtonsOut = m_score->insertMovie( name + " -buttons -out" );
    createLowRightLeafButton( "Documentation", 600, QxMenuManager::DOCUMENTATION, infoButtonsIn, infoButtonsOut, 0 );

    if( !el->inherits( "QxAbstractFactory" ) ) {
      createLowRightLeafButton( "Launch", 405, QxMenuManager::LAUNCH, infoButtonsIn, infoButtonsOut, 0 );
    }
  }

  void createInfo( QxDemoItem* item, const QString& name ) {
    Movie* movie_in = m_score->insertMovie( name );
    Movie* movie_out = m_score->insertMovie( name + " -out" );
    item->setZValue( 8 );
    item->setRecursiveVisible( false );

    float xOffset = 230.0f;
    QxDemoItemAnimation* infoIn = new QxDemoItemAnimation( item, QxDemoItemAnimation::ANIM_IN );
    infoIn->timeline->setCurveShape( QTimeLine::LinearCurve );
    infoIn->setDuration( 650 );
    infoIn->setStartPos( QPointF(m_window->scene->sceneRect().width(), QxColors::contentStartY) );
    infoIn->setPosAt( 0.60, QPointF(xOffset, QxColors::contentStartY) );
    infoIn->setPosAt( 0.70, QPointF(xOffset + 20, QxColors::contentStartY) );
    infoIn->setPosAt( 0.80, QPointF(xOffset, QxColors::contentStartY) );
    infoIn->setPosAt( 0.90, QPointF(xOffset + 7, QxColors::contentStartY) );
    infoIn->setPosAt( 1.00, QPointF(xOffset, QxColors::contentStartY) );
    movie_in->append( infoIn );

    QxDemoItemAnimation* infoOut = new QxDemoItemAnimation( item, QxDemoItemAnimation::ANIM_OUT );
    infoOut->timeline->setCurveShape( QTimeLine::EaseInCurve );
    infoOut->setDuration( 300 );
    infoOut->hideOnFinished = true;
    infoOut->setStartPos( QPointF(xOffset, QxColors::contentStartY) );
    infoOut->setPosAt( 1.0, QPointF(-600, QxColors::contentStartY) );
    movie_out->append( infoOut );
  }

  void createTicker() {
    if( !QxColors::noTicker ){
      Movie* movie_in = m_score->insertMovie( "m_ticker" );
      Movie* movie_out = m_score->insertMovie( "m_ticker -out" );
      Movie* movie_activate = m_score->insertMovie( "m_ticker -activate" );
      Movie* movie_deactivate = m_score->insertMovie( "m_ticker -deactivate" );

      m_ticker = new QxItemCircleAnimation( m_window->scene, 0 );
      m_ticker->setZValue( 50 );
      m_ticker->hide();

      // Move m_ticker in:
      int qtendpos = 485;
      int qtPosY = 120;
      m_tickerInAnim = new QxDemoItemAnimation( m_ticker, QxDemoItemAnimation::ANIM_IN );
      m_tickerInAnim->setDuration( 500 );
      m_tickerInAnim->setStartPos( QPointF(m_window->scene->sceneRect().width(), QxColors::contentStartY + qtPosY) );
      m_tickerInAnim->setPosAt( 0.60, QPointF(qtendpos, QxColors::contentStartY + qtPosY) );
      m_tickerInAnim->setPosAt( 0.70, QPointF(qtendpos + 30, QxColors::contentStartY + qtPosY) );
      m_tickerInAnim->setPosAt( 0.80, QPointF(qtendpos, QxColors::contentStartY + qtPosY) );
      m_tickerInAnim->setPosAt( 0.90, QPointF(qtendpos + 5, QxColors::contentStartY + qtPosY) );
      m_tickerInAnim->setPosAt( 1.00, QPointF(qtendpos, QxColors::contentStartY + qtPosY) );
      movie_in->append( m_tickerInAnim );

      // Move m_ticker out:
      QxDemoItemAnimation* qtOut = new QxDemoItemAnimation( m_ticker, QxDemoItemAnimation::ANIM_OUT );
      qtOut->hideOnFinished = true;
      qtOut->setDuration( 500 );
      qtOut->setStartPos( QPointF(qtendpos, QxColors::contentStartY + qtPosY) );
      qtOut->setPosAt( 1.00, QPointF(m_window->scene->sceneRect().width() + 700, QxColors::contentStartY + qtPosY) );
      movie_out->append( qtOut );

      // Move m_ticker in on activate:
      QxDemoItemAnimation* qtActivate = new QxDemoItemAnimation( m_ticker );
      qtActivate->setDuration( 400 );
      qtActivate->setStartPos( QPointF(m_window->scene->sceneRect().width(), QxColors::contentStartY + qtPosY) );
      qtActivate->setPosAt( 0.60, QPointF(qtendpos, QxColors::contentStartY + qtPosY) );
      qtActivate->setPosAt( 0.70, QPointF(qtendpos + 30, QxColors::contentStartY + qtPosY) );
      qtActivate->setPosAt( 0.80, QPointF(qtendpos, QxColors::contentStartY + qtPosY) );
      qtActivate->setPosAt( 0.90, QPointF(qtendpos + 5, QxColors::contentStartY + qtPosY) );
      qtActivate->setPosAt( 1.00, QPointF(qtendpos, QxColors::contentStartY + qtPosY) );
      movie_activate->append( qtActivate );

      // Move m_ticker out on deactivate:
      QxDemoItemAnimation* qtDeactivate = new QxDemoItemAnimation( m_ticker );
      qtDeactivate->hideOnFinished = true;
      qtDeactivate->setDuration( 400 );
      qtDeactivate->setStartPos( QPointF(qtendpos, QxColors::contentStartY + qtPosY) );
      qtDeactivate->setPosAt( 1.00, QPointF(qtendpos, 800) );
      movie_deactivate->append( qtDeactivate );
    }
  }

  void createUpnDownButtons() {
    float xOffset = 15.0f;
    float yOffset = 450.0f;

    m_upButton = new QxTextButton( "", QxTextButton::LEFT, QxMenuManager::UP, m_window->scene, m_window->mainSceneRoot, QxTextButton::UP );
    m_upButton->prepare();
    m_upButton->setPos( xOffset, yOffset );
    m_upButton->setState( QxTextButton::DISABLED );

    m_downButton = new QxTextButton( "", QxTextButton::LEFT, QxMenuManager::DOWN, m_window->scene, m_window->mainSceneRoot, QxTextButton::DOWN );
    m_downButton->prepare();
    m_downButton->setPos( xOffset + 10 + m_downButton->sceneBoundingRect().width(), yOffset );

    Movie* movieShake = m_score->insertMovie( "upndown -shake" );

    QxDemoItemAnimation *shakeAnim = new QxDemoItemAnimation( m_upButton, QxDemoItemAnimation::ANIM_UNSPECIFIED );
    shakeAnim->timeline->setCurveShape( QTimeLine::LinearCurve );
    shakeAnim->setDuration( 650 );
    shakeAnim->setStartPos( m_upButton->pos() );
    shakeAnim->setPosAt( 0.60, m_upButton->pos() );
    shakeAnim->setPosAt( 0.70, m_upButton->pos() + QPointF(-2, 0) );
    shakeAnim->setPosAt( 0.80, m_upButton->pos() + QPointF(1, 0) );
    shakeAnim->setPosAt( 0.90, m_upButton->pos() + QPointF(-1, 0) );
    shakeAnim->setPosAt( 1.00, m_upButton->pos());
    movieShake->append( shakeAnim );

    shakeAnim = new QxDemoItemAnimation( m_downButton, QxDemoItemAnimation::ANIM_UNSPECIFIED );
    shakeAnim->timeline->setCurveShape( QTimeLine::LinearCurve );
    shakeAnim->setDuration( 650 );
    shakeAnim->setStartPos( m_downButton->pos() );
    shakeAnim->setPosAt( 0.60, m_downButton->pos() );
    shakeAnim->setPosAt( 0.70, m_downButton->pos() + QPointF(-5, 0) );
    shakeAnim->setPosAt( 0.80, m_downButton->pos() + QPointF(-3, 0) );
    shakeAnim->setPosAt( 0.90, m_downButton->pos() + QPointF(-1, 0) );
    shakeAnim->setPosAt( 1.00, m_downButton->pos() );
    movieShake->append( shakeAnim );
  }

  void createBackButton() {
    Movie* backIn = m_score->insertMovie( "back -in" );
    Movie* backOut = m_score->insertMovie( "back -out" );
    Movie* backShake = m_score->insertMovie( "back -shake" );
    createLowLeftButton( QLatin1String("Back"), QxMenuManager::ROOT, backIn, backOut, backShake, QxColors::rootMenuName );
  }

  QString resolveDocUrl( const QString& name ) {
    QString dirName  = m_info[name]["dirname"];
    QString category = m_info[name]["category"];
    QString fileName = m_info[name]["filename"];

    if( category == "demos" ) {
      return m_helpRootUrl + "demos-" + fileName.replace("/", "-") + ".html";
    } else {
      return m_helpRootUrl + dirName.replace("/", "-") + "-" + fileName + ".html";
    }
  }

  QString resolveImageUrl( const QString& name ) {
    return m_helpRootUrl + "images/" + name;
  }

  void slotQuitItemSelected() {
    m_window->loop = false;
    QCoreApplication::quit();
  }

  void slotRootItemSelected( const QString& menuName ) {
    // out:
    m_score->queueMovie( m_currentMenu + " -out", QxScore::FROM_START, QxScore::LOCK_ITEMS );
    m_score->queueMovie( m_currentMenuButtons + " -out", QxScore::FROM_START, QxScore::LOCK_ITEMS );
    m_score->queueMovie( m_currentInfo + " -out" );
    m_score->queueMovie( m_currentInfo + " -buttons -out", QxScore::NEW_ANIMATION_ONLY );
    m_score->queueMovie( "back -out", QxScore::ONLY_IF_VISIBLE );

    // book-keeping:
    m_currentMenuCode = QxMenuManager::ROOT;
    m_currentMenu = menuName + " -menu1";
    m_currentMenuButtons = menuName + " -buttons";
    m_currentInfo = menuName + " -m_info";

    // in:
    m_score->queueMovie( "upndown -shake" );
    m_score->queueMovie( m_currentMenu, QxScore::FROM_START, QxScore::UNLOCK_ITEMS );
    m_score->queueMovie( m_currentMenuButtons, QxScore::FROM_START, QxScore::UNLOCK_ITEMS );
    m_score->queueMovie( m_currentInfo );
    if( !QxColors::noTicker ) {
      m_ticker->doIntroTransitions = true;
      m_tickerInAnim->startDelay = 2000;
      m_ticker->useGuideQt();
      m_score->queueMovie( "m_ticker", QxScore::NEW_ANIMATION_ONLY );
    }
  }

  void slotMenu1ItemSelected( const QString& menuName ) {
    // out:
    m_score->queueMovie( m_currentMenu + " -out", QxScore::FROM_START, QxScore::LOCK_ITEMS );
    m_score->queueMovie( m_currentMenuButtons + " -out", QxScore::FROM_START, QxScore::LOCK_ITEMS );
    m_score->queueMovie( m_currentInfo + " -out" );

    // book-keeping:
    m_currentMenuCode = QxMenuManager::MENU1;
    m_currentCategory = menuName;
    m_currentMenu = menuName + " -menu1";
    m_currentInfo = menuName + " -m_info";
    // in:
    m_score->queueMovie( "upndown -shake" );
    m_score->queueMovie( "back -in" );
    m_score->queueMovie( m_currentMenu, QxScore::FROM_START, QxScore::UNLOCK_ITEMS );
    m_score->queueMovie( m_currentInfo );
    if( !QxColors::noTicker ) {
      m_ticker->useGuideTt();
    }
  }

  void slotMenu2ItemSelected( const QString& menuName ) {
    // out:
    m_score->queueMovie( m_currentInfo + " -out", QxScore::NEW_ANIMATION_ONLY );
    m_score->queueMovie( m_currentInfo + " -buttons -out", QxScore::NEW_ANIMATION_ONLY );

    // book-keeping:
    m_currentMenuCode = QxMenuManager::MENU2;
    m_currentInfo = menuName;
    // in / shake:
    m_score->queueMovie( "upndown -shake" );
    m_score->queueMovie( "back -shake" );
    m_score->queueMovie( m_currentMenu + " -shake" );
    m_score->queueMovie( m_currentInfo, QxScore::NEW_ANIMATION_ONLY );
    m_score->queueMovie( m_currentInfo + " -buttons", QxScore::NEW_ANIMATION_ONLY );
    if( !QxColors::noTicker ) {
      m_score->queueMovie( "m_ticker -out", QxScore::NEW_ANIMATION_ONLY );
    }
  }

  void slotUpItemSelected() {
    QString backMenu = m_info[m_currentMenu]["back"];
    if( !backMenu.isNull() ) {
      m_score->queueMovie( m_currentMenu + " -top_out", QxScore::FROM_START, QxScore::LOCK_ITEMS );
      m_score->queueMovie( backMenu + " -bottom_in", QxScore::FROM_START, QxScore::UNLOCK_ITEMS );
      m_currentMenu = backMenu;
    }
  }

  void slotDownItemSelected() {
    QString moreMenu = m_info[m_currentMenu]["more"];
    if( !moreMenu.isNull() ) {
      m_score->queueMovie( m_currentMenu + " -bottom_out", QxScore::FROM_START, QxScore::LOCK_ITEMS );
      m_score->queueMovie( moreMenu + " -top_in", QxScore::FROM_START, QxScore::UNLOCK_ITEMS );
      m_currentMenu = moreMenu;
    }
  }

  void slotBackItemSelected() {
    if( m_currentMenuCode == QxMenuManager::MENU2 ) {
      // out:
      m_score->queueMovie( m_currentInfo + " -out", QxScore::NEW_ANIMATION_ONLY );
      m_score->queueMovie( m_currentInfo + " -buttons -out", QxScore::NEW_ANIMATION_ONLY );

      // book-keeping:
      m_currentMenuCode = QxMenuManager::MENU1;
      m_currentMenuButtons = m_currentCategory + " -buttons";
      m_currentInfo = m_currentCategory + " -m_info";

      // in / shake:
      m_score->queueMovie( "upndown -shake" );
      m_score->queueMovie( m_currentMenu + " -shake" );
      m_score->queueMovie( m_currentInfo, QxScore::NEW_ANIMATION_ONLY );
      m_score->queueMovie( m_currentInfo + " -buttons", QxScore::NEW_ANIMATION_ONLY );
      if( !QxColors::noTicker ) {
        m_ticker->doIntroTransitions = false;
        m_tickerInAnim->startDelay = 500;
        m_score->queueMovie( "m_ticker", QxScore::NEW_ANIMATION_ONLY );
      }
    } else if( m_currentMenuCode != QxMenuManager::ROOT ) {
      m_self->itemSelected( QxMenuManager::ROOT, QxColors::rootMenuName );
    }
  }

  QxMenuManager*           m_self;

  HashHash                 m_info;
  QxItemCircleAnimation*   m_ticker;
  QxMainWindow*            m_window;
  QxScore*                 m_score;
  int                      m_currentMenuCode;

#ifndef QT_NO_DECLARATIVE
  QDeclarativeEngine*      m_declarativeEngine;
#endif

  QProcess                 m_assistantProcess;
  QString                  m_currentMenu;
  QString                  m_currentCategory;
  QString                  m_currentMenuButtons;
  QString                  m_currentInfo;
  QString                  m_helpRootUrl;
  QxDemoItemAnimation*     m_tickerInAnim;
  QDir                     m_docDir;
  QDir                     m_dimgDir;
  QHelpEngineCore*         m_helpEngine;

  QxTextButton*            m_upButton;
  QxTextButton*            m_downButton;
};

QxMenuManager* QxMenuManager::instance()
{
  if( !QxMenuManager::pInstance ) {
    QxMenuManager::pInstance = new QxMenuManager();
  }
  return QxMenuManager::pInstance;
}

QxMenuManager::~QxMenuManager()
{
}

void QxMenuManager::init( QxMainWindow* window )
{
  _pd->init( window );
}

void QxMenuManager::itemSelected( int userCode, const QString& menuName )
{
  switch( userCode ) {
  case LAUNCH:        _pd->launchExample( _pd->m_currentInfo );      break;

  case DOCUMENTATION: _pd->showDocInAssistant( _pd->m_currentInfo ); break;

  case QUIT:          _pd->slotQuitItemSelected();                   break;

  case FULLSCREEN:    _pd->m_window->toggleFullscreen();             break;

  case ROOT:          _pd->slotRootItemSelected( menuName );         break;

  case MENU1:         _pd->slotMenu1ItemSelected( menuName );        break;

  case MENU2:         _pd->slotMenu2ItemSelected( menuName );        break;

  case UP:            _pd->slotUpItemSelected();                     break;

  case DOWN:          _pd->slotDownItemSelected();                   break;

  case BACK:          _pd->slotBackItemSelected();                   break;
  }

  // update back- and more buttons
  bool noBackMenu = _pd->m_info[_pd->m_currentMenu]["back"].isNull();
  bool noMoreMenu = _pd->m_info[_pd->m_currentMenu]["more"].isNull();
  _pd->m_upButton->setState( noBackMenu ? QxTextButton::DISABLED : QxTextButton::OFF );
  _pd->m_downButton->setState( noMoreMenu ? QxTextButton::DISABLED : QxTextButton::OFF );

  if( _pd->m_score->hasQueuedMovies() ) {
    _pd->m_score->playQue();
    // Playing new movies might include
    // loading etc. So ignore the FPS
    // at this point
    _pd->m_window->fpsHistory.clear();
  }
}

int QxMenuManager::currentMenuCode() const
{
  return _pd->m_currentMenuCode;
}

const HashHash&QxMenuManager::info() const
{
  return _pd->m_info;
}

QxMainWindow*QxMenuManager::window() const
{
  return _pd->m_window;
}

QxItemCircleAnimation*QxMenuManager::ticker() const
{
  return _pd->m_ticker;
}

QByteArray QxMenuManager::getHtml( const QString& name )
{
  return _pd->getResource( _pd->resolveDocUrl( name ) );
}

QByteArray QxMenuManager::getImage( const QString& name )
{
  QString imageName = _pd->m_info[name]["image"];
  QString category = _pd->m_info[name]["category"];
  QString fileName = _pd->m_info[name]["filename"];
  bool qml = (_pd->m_info[name]["qml"] == QLatin1String("true"));
  if( qml ) {
    fileName = QLatin1String("qml-") + fileName.split('/').last();
  }

  if( imageName.isEmpty() ) {
    if( category == "demos" ) {
      imageName = fileName + "-demo.png";
    } else {
      imageName = fileName + "-example.png";
    }
    if( (_pd->getResource( _pd->resolveImageUrl(imageName))).isEmpty() ) {
      imageName = fileName + ".png";
    }
    if( (_pd->getResource( _pd->resolveImageUrl(imageName))).isEmpty() ) {
      imageName = fileName + "example.png";
    }
  }
  return _pd->getResource( _pd->resolveImageUrl( imageName ) );
}

void QxMenuManager::exampleFinished()
{
}

void QxMenuManager::exampleError( QProcess::ProcessError error )
{
  if( error != QProcess::Crashed ) {
    QString caption = tr( "Failed to launch the example" );
    QString text = tr( "Could not launch the example. Ensure that it has been built." );
    QMessageBox::critical(0, caption, text, QMessageBox::Cancel );
  }
}

QxMenuManager::QxMenuManager() : _pd( new QxMenuManagerPrivate( this ) )
{
}
